#include "OpenGLAPI.h"

#include "OpenGLBase.h"

#include <GLFW/glfw3.h>

namespace SE
{
    Ref<RenderContext> RenderAPI::CreateContext()
    {
        SE_PROFILE_FUNCTION();

        auto assetsDir = GlobalContext::GetConfigManager()->getAssetsDir();

        auto newContext = std::make_shared<RenderContext>();
        
        //-----------------------------------------------------------------------------------------------------------------------
        /* Screen buffer */
        newContext->background = FrameBuffer::Create({
            // Color buffer.
            {
                FrameBuffer::AttachmentType::Color,
                FrameBuffer::AttachmentBufferType::Texture,
                RenderInternalFormat::RGBA32F,
                RenderDataFormat::RGBA
            },
            // Depth and stencil buffer.
            {
                FrameBuffer::AttachmentType::DepthAndStencil,
                FrameBuffer::AttachmentBufferType::RenderBufferObject,
                RenderInternalFormat::Depth24FStencil8F,
                RenderDataFormat::DepthAndStencil
            }
            });
        newContext->background->setBuffer(1280, 720);
        //-----------------------------------------------------------------------------------------------------------------------


        //-----------------------------------------------------------------------------------------------------------------------
        /* Triangle */
        newContext->triangleShader = Shader::Create(assetsDir + "/shaders/Renderer2D_Triangle.glsl");

        // VertexArray.
        newContext->triangleVAO = VertexArray::Create();

        // Indices.
        auto triangleIBO = IndexBuffer::Create(newContext->MaxTriangles * 3);
        newContext->triangleVAO->setIndexBuffer(triangleIBO);

        // Vertices.
        newContext->triangleVBO = VertexBuffer::Create(newContext->MaxTriangles * 3 * sizeof(RenderVertex));
        newContext->triangleVBO->setLayout({
            { ShaderDataType::Float3, "Positions"     },
            { ShaderDataType::Float3, "Normals"       },
            { ShaderDataType::Float4, "Colors"        },
            { ShaderDataType::Float2, "TexCoords"     },
            { ShaderDataType::Mat4,   "Transforms"    },
            { ShaderDataType::Float,  "TexIndexs"     },
            { ShaderDataType::Float,  "TilingFactors" },
            { ShaderDataType::Int,    "EntityIDs"     }
            });
        newContext->triangleVAO->addVertexBuffer(newContext->triangleVBO);
        newContext->triangleVAO->unbind();
        //-----------------------------------------------------------------------------------------------------------------------


        //-----------------------------------------------------------------------------------------------------------------------
        /* Quad */
        newContext->quadShader = Shader::Create(assetsDir + "/shaders/Renderer2D_Quad.glsl");

        // VertexArray.
        newContext->quadVAO = VertexArray::Create();

        // Indices.
        U32* quadIndices = new U32[newContext->MaxQuads * 6];
        for (U32 i = 0; i < newContext->MaxQuads; ++i)
        {
            quadIndices[i * 6 + 0] = i * 4 + 0;
            quadIndices[i * 6 + 1] = i * 4 + 1;
            quadIndices[i * 6 + 2] = i * 4 + 2;
            quadIndices[i * 6 + 3] = i * 4 + 0;
            quadIndices[i * 6 + 4] = i * 4 + 2;
            quadIndices[i * 6 + 5] = i * 4 + 3;
        }
        newContext->quadVAO->setIndexBuffer(IndexBuffer::Create(newContext->MaxQuads * 6, quadIndices));
        delete[] quadIndices;

        // Vertices.
        newContext->quadVBO = VertexBuffer::Create(newContext->MaxQuads * 4 * sizeof(RenderVertex));
        newContext->quadVBO->setLayout({
            { ShaderDataType::Float3, "Positions"     },
            { ShaderDataType::Float3, "Normals"       },
            { ShaderDataType::Float4, "Colors"        },
            { ShaderDataType::Float2, "TexCoords"     },
            { ShaderDataType::Mat4,   "Transforms"    },
            { ShaderDataType::Float,  "TexIndexs"     },
            { ShaderDataType::Float,  "TilingFactors" },
            { ShaderDataType::Int,    "EntityIDs"     }
            });
        newContext->quadVAO->addVertexBuffer(newContext->quadVBO);
        newContext->quadVAO->unbind();
        //-----------------------------------------------------------------------------------------------------------------------


        //-----------------------------------------------------------------------------------------------------------------------
        /* Texture */
        U32 white = 0xffffffff;
        U32 size[] = { 1u, 1u };
        auto textureBuffer = TextureBuffer::Create(
            2,
            size,
            RenderInternalFormat::RGBA8,
            TextureBuffer::WrapMode::Repeat,
            TextureBuffer::MinFilterMode::Linear,
            TextureBuffer::MagFilterMode::Linear,
            false,
            false);
        textureBuffer->setBuffer(&white, size, 2);
        newContext->whiteTexture = Texture::Create(textureBuffer);
        newContext->textureSlots[0] = newContext->whiteTexture;
        //-----------------------------------------------------------------------------------------------------------------------

        return newContext;
    }

    void OpenGLMessageCallback(
        unsigned source,
        unsigned type,
        unsigned id,
        unsigned severity,
        int length,
        const char* message,
        const void* userParam)
    {
        switch (severity)
        {
            default: SE_ASSERT(false, "Unknown severity level!"); break;
            case GL_DEBUG_SEVERITY_HIGH:         LOG_CRITI("OpenGL debug callback: {}", message); break;
            case GL_DEBUG_SEVERITY_MEDIUM:       LOG_ERROR("OpenGL debug callback: {}", message); break;
            case GL_DEBUG_SEVERITY_LOW:          LOG_WARN("OpenGL debug callback: {}", message); break;
            case GL_DEBUG_SEVERITY_NOTIFICATION: LOG_INFO("OpenGL debug callback: {}", message); break;
        }
    }

    void RenderAPI::DestoryContext(const Ref<RenderContext>& context)
    {
    }

    OpenGLAPI::OpenGLAPI()
    {
        SE_PROFILE_FUNCTION();

        I32 status = gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
        SE_ASSERT(status, "Failed to initialize Glad!");

        LOG_INFO(
            "OpenGL Info:\nVendor: \t{0}\nRenderer:\t{1}\nVersion:\t{2}",
            (char*)glGetString(GL_VENDOR),
            (char*)glGetString(GL_RENDERER),
            (char*)glGetString(GL_VERSION));

        SE_ASSERT(
            GLVersion.major > 4 || (GLVersion.major == 4 && GLVersion.minor >= 5),
            "StudyEngine requires at least OpenGL version 4.5!");

#ifdef SE_DEBUG
        glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);

        glEnable(GL_DEBUG_OUTPUT);
        glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
        glDebugMessageCallback(OpenGLMessageCallback, nullptr);
        glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_NOTIFICATION, 0, NULL, GL_FALSE);
#endif

        GLCALL(glEnable(GL_BLEND));
        GLCALL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));

        GLCALL(glEnable(GL_DEPTH_TEST));
    }

    OpenGLAPI::~OpenGLAPI()
    {
    }

    RenderAPIType OpenGLAPI::getAPIType() const
    {
        return RenderAPIType::OpenGL;
    }

    void OpenGLAPI::makeContextCurrent(const Ref<RenderContext>& context)
    {
        SE_ASSERT(context, "Empty context!");

        mCurrentContext = context;
    }

    void OpenGLAPI::setViewport(const U32 x, const U32 y, const U32 width, const U32 height)
    {
        SE_PROFILE_FUNCTION();

        glViewport(x, y, width, height);
    }

    void OpenGLAPI::setClearColor(const Vector4f& color)
    {
        SE_PROFILE_FUNCTION();

        glClearColor(color.x, color.y, color.z, color.w);
    }

    void OpenGLAPI::clear()
    {
        SE_PROFILE_FUNCTION();

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    }

    void OpenGLAPI::drawIndexed(const Ref<VertexArray>& vertexArray, const U64 indexCount)
    {
        SE_PROFILE_FUNCTION();

        vertexArray->bind();
        U64 count = indexCount ? indexCount : vertexArray->getIndexBuffer()->getCount();
        GLCALL(glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_INT, nullptr));
    }

    void OpenGLAPI::drawArrays(const Ref<VertexArray>& vertexArray, const U64 vertexCount)
    {
        SE_PROFILE_FUNCTION();

        vertexArray->bind();
        U64 count = vertexCount ? vertexCount : vertexArray->getIndexBuffer()->getCount();
        GLCALL(glDrawArrays(GL_TRIANGLES, 0, count));
    }

    void OpenGLAPI::drawLines(const Ref<VertexArray>& vertexArray, const U64 vertexCount)
    {
        SE_PROFILE_FUNCTION();

        vertexArray->bind();
        U64 count = vertexCount ? vertexCount : vertexArray->getIndexBuffer()->getCount();
        glDrawArrays(GL_LINES, 0, count);
    }

    void OpenGLAPI::setLineWidth(const Float width)
    {
        SE_PROFILE_FUNCTION();

        glLineWidth(width);
    }

    void OpenGLAPI::beginScene(const CameraData cameraData)
    {
        SE_PROFILE_FUNCTION();

        SE_ASSERT(!mCurrentContext.expired(), "Current context is already expired!");
        mCurrentContext.lock()->cameraData = cameraData;

        startBatch();
    }

    void OpenGLAPI::endScene()
    {
        SE_PROFILE_FUNCTION();

        flush();
    }

    void OpenGLAPI::startBatch()
    {
        SE_PROFILE_FUNCTION();


        SE_ASSERT(!mCurrentContext.expired(), "Current context is already expired!");
        auto currentContext = mCurrentContext.lock();

        currentContext->triangleIndex = 0;
        currentContext->quadIndex = 0;
        currentContext->textureSlotIndex = 1;
    }

    void OpenGLAPI::nextBatch()
    {
        SE_PROFILE_FUNCTION();

        flush();
        startBatch();
    }

    void OpenGLAPI::flush()
    {
        SE_PROFILE_FUNCTION();

        SE_ASSERT(!mCurrentContext.expired(), "Current context is already expired!");
        auto currentContext = mCurrentContext.lock();

        // Draw triangles.
        if (currentContext->triangleIndex)
        {
            currentContext->triangleVBO->setBuffer(
                currentContext->triangleVertices.data(),
                currentContext->triangleIndex * 3 * sizeof(RenderVertex));

            // Bind textures.
            for (U32 i = 0; i < currentContext->textureSlotIndex; i++)
            {
                currentContext->textureSlots[i]->bind(i);
            }

            currentContext->triangleVAO->bind();
            currentContext->triangleShader->bind();
            currentContext->triangleShader->setUniformFloat3("cameraPos", currentContext->cameraData.cameraPosition);  // Temporary.
            currentContext->triangleShader->setUniformMatrix4f("ViewMatrix", currentContext->cameraData.viewMatrix);  // Temporary.
            currentContext->triangleShader->setUniformMatrix4f("ProjectionMatrix", currentContext->cameraData.projectionMatrix);  // Temporary.
            drawArrays(currentContext->triangleVAO, currentContext->triangleIndex * 3);
            currentContext->drawCalls++;
        }

        // Draw quads.
        if (currentContext->quadIndex)
        {
            currentContext->quadVBO->setBuffer(
                currentContext->quadVertices.data(),
                currentContext->quadIndex * 4 * sizeof(RenderVertex));

            // Bind textures.
            for (U32 i = 0; i < currentContext->textureSlotIndex; i++)
            {
                currentContext->textureSlots[i]->bind(i);
            }

            currentContext->quadVAO->bind();
            currentContext->quadShader->bind();
            currentContext->quadShader->setUniformFloat3("cameraPos", currentContext->cameraData.cameraPosition);  // Temporary.
            currentContext->quadShader->setUniformMatrix4f("ViewMatrix", currentContext->cameraData.viewMatrix);  // Temporary.
            currentContext->quadShader->setUniformMatrix4f("ProjectionMatrix", currentContext->cameraData.projectionMatrix);  // Temporary.
            drawIndexed(currentContext->quadVAO, currentContext->quadIndex * 6);
            currentContext->drawCalls++;
        }
    }

    void OpenGLAPI::resetStats()
    {
        SE_PROFILE_FUNCTION();

        SE_ASSERT(!mCurrentContext.expired(), "Current context is already expired!");

        auto currentContext = mCurrentContext.lock();

        currentContext->drawCalls = 0;
    }

    void OpenGLAPI::enableTests(const EnabledTest enabledTests)
    {

    }

    void OpenGLAPI::disableTests(const EnabledTest enabledTests)
    {

    }
    
    void OpenGLAPI::submitTriangles(
        RenderVertex*   vertices,
        const U32       vertexCount,
        U32*            indices,
        const U32       indexCount)
    {
        SE_PROFILE_FUNCTION();

        SE_ASSERT(!mCurrentContext.expired(), "Current context is already expired!");
        auto currentContext = mCurrentContext.lock();

        // All the arguments are under INTERVAL mode of data storage.
        // 
        // Check whether the new indices buffer size is greater than the rest of triangle indices buffer size:
        // 1. If yes, check whether the new indices buffer size is greater than the max triangle indices buffer size.
        //    (1) If yes, that's awful, but we can choose one of following steps:
        //        1. We can use bigger triangle buffers. (This is not a good solution, but useful.)
        //        2. We can slice this new buffers (both vertices and indices!), so that we can push them into our original triangle buffers.
        //           This is not so easy, but we can still do as following steps:
        //           (1) ...
        //           (2) ...
        //    (2) If no, that means we can use our original triangle buffers, we can try as following steps:
        //        1. Check whether the new vertices buffer size is greater than the rest of triangle vertices buffer size:
        //           (1) If yes, we just invoke 'nextBatch' function and push them again, then everything will go well.
        //           (2) If no, there are two solutions:
        //               1. We just invoke 'nextBatch' function  and push them again, then everything will go well as well, but this will waste render calls.
        //               2. We can slice the new indices buffer up to run out the rest of triangle indices buffer, also remember push new vertices buffer to the rest of triangle buffer,
        //                  then invoke 'nextBatch' function. Now, we can push new vertices buffer again, and also push the rest of new indices buffer.
        // 2. If no, we just push both new vertices and new indices buffer. Remember that we should pad new indices buffer before pushing.
        // 
        // Remember that, it's time-consuming to slice new vertices buffer, but easy to slice new indices buffer.
        // We can just invoke 'nextBatch' to simplify the processes if new indices buffer is less than the max indices buffer.

        // Check whether the new indices buffer size is greater than the rest of triangle indices buffer size.
        // Equation is valid.
        //
        //if (indexCount > RenderContext::MaxTriangles * 3 - currentContext->triangleIndexIndex)
        //{
        //    // Check whether the new indices buffer size is greater than the max triangle indices buffer size.
        //    // Equation is valid.
        //    if (indexCount > RenderContext::MaxTriangles * 3)
        //    {
        //        // TODO: Implement the following.
        //        SE_ASSERT(false, "The new indices buffer is bigger than the max triangle indices buffer!");
        //    }
        //    else
        //    {
        //        // Now means we can use our original triangle buffers.
        //        // For now, we just simply invoke 'nextBatch' function and push them again to simplify the processes.
        //        nextBatch();
        //        submitTriangles(vertices, vertexCount, indices, indexCount);
        //    }
        //}
        //else
        //{
        //    /* Just push both new vertices and new indices buffer. */
        //    // Push new indices.
        //    // Pad new indices before pushing.
        //    U32* paddingIndices = new U32[indexCount];
        //    U32 pad = currentContext->triangleVertexIndex;
        //    for (U32 i = 0; i < indexCount; ++i)
        //    {
        //        paddingIndices[i] = pad + indices[i];
        //    }
        //    memcpy_s(
        //        &currentContext->triangleIndices[currentContext->triangleIndexIndex],
        //        (RenderContext::MaxTriangles * 3 - currentContext->triangleIndexIndex) * sizeof(U32),
        //        paddingIndices,
        //        indexCount * sizeof(U32));
        //    // Delete template buffer.
        //    delete[] paddingIndices;
        //    paddingIndices = nullptr;
        //    // Update index.
        //    currentContext->triangleIndexIndex += indexCount;
        //    SE_ASSERT(currentContext->triangleIndexIndex <= RenderContext::MaxTriangles * 3, "Awful!");

        //    // Push new vertices.
        //    memcpy_s(
        //        &currentContext->triangleVertices[currentContext->triangleVertexIndex],
        //        (RenderContext::MaxTriangles * 3 - currentContext->triangleVertexIndex) * sizeof(RenderVertex),
        //        vertices,
        //        vertexCount * sizeof(RenderVertex));
        //    // Update index.
        //    currentContext->triangleVertexIndex += vertexCount;
        //    SE_ASSERT(currentContext->triangleVertexIndex <= RenderContext::MaxTriangles * 3, "Awful!");

        //    // Update render statistic.
        //    currentContext->triangleCount += indexCount / 3;
        //}
    }

    void OpenGLAPI::submitTriangles(
        const RenderVertex2D*   vertices,
        const U32               triangleCount,
        const Matrix4f&         transform,
        const Ref<Texture>&     texture,
        const Float             tilingFactor,
        const I32               entityID)
    {
        SE_PROFILE_FUNCTION();

        SE_ASSERT(!mCurrentContext.expired(), "Current context is already expired!");
        auto currentContext = mCurrentContext.lock();

        if (currentContext->triangleIndex >= RenderContext::MaxTriangles)
            nextBatch();

        // Find whether contains the same texture with new texture.
        // If the new texture is 'nullptr', that means we just use the white texture as default.
        Float textureIndex = 0.0f;
        if (texture)
        {
            for (U32 i = 1; i < currentContext->textureSlotIndex; i++)
            {
                // If find the same one, we just use the same 'textureIndex'.
                if (*currentContext->textureSlots[i] == *texture)
                {
                    textureIndex = (Float)i;
                    break;
                }
            }

            // 'textureIndex == 0.0f' means the new texture is actually the new one.
            if (textureIndex == 0.0f)
            {
                if (currentContext->textureSlotIndex >= RenderContext::MaxTextureSlots)
                    nextBatch();

                textureIndex = (Float)currentContext->textureSlotIndex;
                currentContext->textureSlots[currentContext->textureSlotIndex] = texture;
                currentContext->textureSlotIndex++;
            }
        }

        for (U32 i = 0; i < triangleCount; ++i)
        {
            for (U32 j = 0; j < 3; ++j)
            {
                U64 vertexIndex = i * 3 + j;
                auto& newVertex = currentContext->triangleVertices[currentContext->triangleIndex * 3 + vertexIndex];

                newVertex.position      = transform * Vector4f(vertices[vertexIndex].position.x, vertices[vertexIndex].position.y, 0.0f, 1.0f);
                newVertex.normal        = Vector3f(0.0f, 0.0f, 1.0f);
                newVertex.color         = vertices[vertexIndex].color;
                newVertex.texCoord      = vertices[vertexIndex].texCoord;
                newVertex.transform     = transform;
                newVertex.texIndex      = textureIndex;
                newVertex.tilingFactor  = tilingFactor;
                newVertex.entityID      = entityID;
            }
        }
        currentContext->triangleIndex += triangleCount;
    }

    void OpenGLAPI::submitTriangles(
        const RenderVertex3D*   vertices,
        const U32               triangleCount,
        const Matrix4f&         transform,
        const Ref<Texture>&     texture,
        const Float             tilingFactor,
        const I32               entityID)
    {
        SE_PROFILE_FUNCTION();

        SE_ASSERT(!mCurrentContext.expired(), "Current context is already expired!");
        auto currentContext = mCurrentContext.lock();

        if (currentContext->triangleIndex + triangleCount > RenderContext::MaxTriangles)
        {
            nextBatch();
        }

        // Find whether contains the same texture with new texture.
        // If the new texture is 'nullptr', that means we just use the white texture as default.
        Float textureIndex = 0.0f;
        if (texture)
        {
            for (U32 i = 1; i < currentContext->textureSlotIndex; i++)
            {
                // If find the same one, we just use the same 'textureIndex'.
                if (*currentContext->textureSlots[i] == *texture)
                {
                    textureIndex = (Float)i;
                    break;
                }
            }

            // 'textureIndex == 0.0f' means the new texture is actually the new one.
            if (textureIndex == 0.0f)
            {
                if (currentContext->textureSlotIndex >= RenderContext::MaxTextureSlots)
                    nextBatch();

                textureIndex = (Float)currentContext->textureSlotIndex;
                currentContext->textureSlots[currentContext->textureSlotIndex] = texture;
                currentContext->textureSlotIndex++;
            }
        }

        for (U32 i = 0; i < triangleCount; ++i)
        {
            for (U32 j = 0; j < 3; ++j)
            {
                U64 vertexIndex = i * 3 + j;
                auto& newVertex = currentContext->triangleVertices[currentContext->triangleIndex * 3 + vertexIndex];

                newVertex.position      = Vector4f(vertices[vertexIndex].position.x, vertices[vertexIndex].position.y, vertices[vertexIndex].position.z, 1.0f);
                newVertex.normal        = vertices[vertexIndex].normal;
                newVertex.color         = vertices[vertexIndex].color;
                newVertex.texCoord      = vertices[vertexIndex].texCoord;
                newVertex.transform     = transform;
                newVertex.texIndex      = textureIndex;
                newVertex.tilingFactor  = tilingFactor;
                newVertex.entityID      = entityID;
            }
        }
        currentContext->triangleIndex += triangleCount;
    }

    void OpenGLAPI::submitQuads(
        const RenderVertex2D*   vertices,
        const U32               quadCount,
        const Matrix4f&         transform,
        const Ref<Texture>&     texture,
        const Float             tilingFactor,
        const I32               entityID)
    {
        SE_PROFILE_FUNCTION();

        SE_ASSERT(!mCurrentContext.expired(), "Current context is already expired!");
        auto currentContext = mCurrentContext.lock();

        if (currentContext->quadIndex >= RenderContext::MaxQuads)
            nextBatch();

        // Find whether contains the same texture with new texture.
        // If the new texture is 'nullptr', that means we just use the white texture as default.
        Float textureIndex = 0.0f;
        if (texture)
        {
            for (U32 i = 1; i < currentContext->textureSlotIndex; i++)
            {
                // If find the same one, we just use the same 'textureIndex'.
                if (*currentContext->textureSlots[i] == *texture)
                {
                    textureIndex = (Float)i;
                    break;
                }
            }

            // 'textureIndex == 0.0f' means the new texture is actually the new one.
            if (textureIndex == 0.0f)
            {
                if (currentContext->textureSlotIndex >= RenderContext::MaxTextureSlots)
                    nextBatch();

                textureIndex = (Float)currentContext->textureSlotIndex;
                currentContext->textureSlots[currentContext->textureSlotIndex] = texture;
                currentContext->textureSlotIndex++;
            }
        }

        for (U32 i = 0; i < quadCount; ++i)
        {
            for (U32 j = 0; j < 4; ++j)
            {
                U64 vertexIndex = i * 4 + j;
                auto& newVertex = currentContext->quadVertices[currentContext->quadIndex * 4 + vertexIndex];

                newVertex.position      = transform * Vector4f(vertices[vertexIndex].position.x, vertices[vertexIndex].position.y, 0.0f, 1.0f);
                newVertex.normal        = Vector3f(0.0f, 0.0f, 1.0f);
                newVertex.color         = vertices[vertexIndex].color;
                newVertex.texCoord      = vertices[vertexIndex].texCoord;
                newVertex.texIndex      = textureIndex;
                newVertex.tilingFactor  = tilingFactor;
                newVertex.entityID      = entityID;
            }
        }
        currentContext->quadIndex += quadCount;
    }

    void OpenGLAPI::submitQuads(
        const RenderVertex3D*   vertices,
        const U32               quadCount,
        const Matrix4f&         transform,
        const Ref<Texture>&     texture,
        const Float             tilingFactor,
        const I32               entityID)
    {
        SE_PROFILE_FUNCTION();

        SE_ASSERT(!mCurrentContext.expired(), "Current context is already expired!");
        auto currentContext = mCurrentContext.lock();

        if (currentContext->quadIndex >= RenderContext::MaxQuads)
            nextBatch();

        // Find whether contains the same texture with new texture.
        // If the new texture is 'nullptr', that means we just use the white texture as default.
        Float textureIndex = 0.0f;
        if (texture)
        {
            for (U32 i = 1; i < currentContext->textureSlotIndex; i++)
            {
                // If find the same one, we just use the same 'textureIndex'.
                if (*currentContext->textureSlots[i] == *texture)
                {
                    textureIndex = (Float)i;
                    break;
                }
            }

            // 'textureIndex == 0.0f' means the new texture is actually the new one.
            if (textureIndex == 0.0f)
            {
                if (currentContext->textureSlotIndex >= RenderContext::MaxTextureSlots)
                    nextBatch();

                textureIndex = (Float)currentContext->textureSlotIndex;
                currentContext->textureSlots[currentContext->textureSlotIndex] = texture;
                currentContext->textureSlotIndex++;
            }
        }

        for (U32 i = 0; i < quadCount; ++i)
        {
            for (U32 j = 0; j < 4; ++j)
            {
                U64 vertexIndex = i * 4 + j;
                auto& newVertex = currentContext->quadVertices[currentContext->quadIndex * 4 + vertexIndex];

                newVertex.position      = transform * Vector4f(vertices[vertexIndex].position.x, vertices[vertexIndex].position.y, vertices[vertexIndex].position.z, 1.0f);
                newVertex.normal        = vertices[vertexIndex].normal;
                newVertex.color         = vertices[vertexIndex].color;
                newVertex.texCoord      = vertices[vertexIndex].texCoord;
                newVertex.texIndex      = textureIndex;
                newVertex.tilingFactor  = tilingFactor;
                newVertex.entityID      = entityID;
            }
        }
        currentContext->quadIndex += quadCount;
    }
} // !namespace SE